﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LinAlg3D
{
    /// <summary>
    /// Describes and manipulates a vector in three-dimensional (3-D) space.
    /// </summary>
    [Serializable()]
    public struct Vector3
    {
        private float x;
        private float y;
        private float z;

        /// <summary>
        /// Gets and sets the x component of the vector.
        /// </summary>
        public float X
        {
            get { return x; }
            set { x = value; }
        }

        /// <summary>
        /// Gets and sets the y component of the vector.
        /// </summary>
        public float Y
        {
            get { return y; }
            set { y = value; }
        }

        /// <summary>
        /// Gets and sets the z component of the vector.
        /// </summary>
        public float Z
        {
            get { return z; }
            set { z = value; }
        }

        #region Constructor
        /// <summary>
        /// Initializes a new instance of the Vector3 struct.
        /// </summary>
        /// <param name="x">x component of the vector.</param>
        /// <param name="y">y component of the vector.</param>
        /// <param name="z">z component of the vector.</param>
        public Vector3(float x, float y, float z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }
        #endregion

        // Properties

        #region Empty
        /// <summary>
        /// Retrieves an empty 3-D vector.
        /// </summary>
        public static Vector3 Empty
        {
            get
            {
                return new Vector3(0, 0, 0);
            }
        }
        #endregion

        #region UnitX
        /// <summary>
        /// Retrieves an unit vector along the x-axis.
        /// </summary>
        public static Vector3 UnitX
        {
            get
            {
                return new Vector3(1, 0, 0);
            }
        }
        #endregion

        #region UnitY
        /// <summary>
        /// Retrieves an unit vector along the y-axis.
        /// </summary>
        public static Vector3 UnitY
        {
            get
            {
                return new Vector3(0, 1, 0);
            }
        }
        #endregion

        #region UnitZ
        /// <summary>
        /// Retrieves an unit vector along the z-axis.
        /// </summary>
        public static Vector3 UnitZ
        {
            get
            {
                return new Vector3(0, 0, 1);
            }
        }
        #endregion

        // Methods

        #region Addition

        /// <summary>
        /// Adds two 3-D vectors.
        /// </summary>
        /// <param name="vector"></param>
        public void Add(Vector3 vector)
        {
            this += vector;
        }

        /// <summary>
        /// Adds two 3-D vectors.
        /// </summary>
        /// <param name="vector1">Source Vector3.</param>
        /// <param name="vector2">Source Vector3.</param>
        /// <returns>Sum of the two Vector3 structures.</returns>
        public static Vector3 Add(Vector3 vector1, Vector3 vector2)
        {
            return new Vector3(vector1.x + vector2.x, vector1.y + vector2.y, vector1.z + vector2.z);
        }

        #endregion

        #region CrossProduct
        /// <summary>
        /// Determines the cross product of two 3-D vectors. The operation is not cummatative!
        /// </summary>
        /// <param name="left">Source Vector3f structure.</param>
        /// <param name="right">Source Vector3f structure.</param>
        /// <returns>A Vector3f structure that is the cross product of two 3-D vectors.</returns>
        public static Vector3 Cross(Vector3 left, Vector3 right)
        {
            Vector3 result;
            result.x = left.y * right.z - left.z * right.y;
            result.y = left.z * right.x - left.x * right.z;
            result.z = left.x * right.y - left.y * right.x;
            return result;
        }
        #endregion

        #region DotProduct
        /// <summary>
        /// Determines the dot product of two 3-D vectors.
        /// </summary>
        /// <param name="vector1">Source Vector3 structure.</param>
        /// <param name="vector2">Source Vector3 structure.</param>
        /// <returns>A Single value that is the dot product.</returns>
        public static float Dot(Vector3 vector1, Vector3 vector2)
        {
            return vector1.x * vector2.x + vector1.y * vector2.y + vector1.z * vector2.z;
        }

        /// <summary>
        /// Determines the dot product of two 3-D vectors.
        /// </summary>
        /// <param name="vector1">Source Vector3 structure.</param>
        /// <param name="vector2">Source Vector3 structure.</param>
        /// <returns>A Single value that is the dot product.</returns>
        public static float operator *(Vector3 vector1, Vector3 vector2)
        {
            return vector1.x * vector2.x + vector1.y * vector2.y + vector1.z * vector2.z;
        }
        #endregion

        #region Equals
        /// <summary>
        /// Returns a value that indicates whether the current instance is equal to a specified object.
        /// </summary>
        /// <param name="obj">Object with which to make the comparison.</param>
        /// <returns>Value that is true if the current instance is equal to the specified object, or false if it is not.</returns>
        public override bool Equals(object obj)
        {
            // If parameter is null return false.
            if (obj == null)
            {
                return false;
            }

            // If parameter cannot be cast to Vector3f return false.
            if (!(obj is Vector3))
            {
                return false;
            }

            // Return true if the fields match:
            return (x == ((Vector3)obj).x && y == ((Vector3)obj).y && z == ((Vector3)obj).z);
        }
        #endregion

        #region GetHashCode
        /// <summary>
        /// Returns the hash code for the current instance.
        /// </summary>
        /// <returns>Hash code for the instance.</returns>
        public override int GetHashCode()
        {
            return x.GetHashCode() ^ y.GetHashCode() ^ z.GetHashCode();
        }
        #endregion

        #region Length
        /// <summary>
        /// Returns the length of a 3-D vector.
        /// </summary>
        /// <param name="vector">Source Vector3f structure.</param>
        /// <returns>A Single value that contains the vector's length.</returns>
        public static float Length(Vector3 vector)
        {
            return vector.Length();
        }

        /// <summary>
        /// Returns the length of a 3-D vector.
        /// </summary>
        /// <returns>A Single value that contains the vector's length.</returns>
        public float Length()
        {
            return (float)System.Math.Sqrt(x * x + y * y + z * z);
        }
        #endregion

        #region LengthSquared
        /// <summary>
        /// Returns the square of the length of a 3-D vector.
        /// </summary>
        /// <param name="vector">Source Vector3 structure.</param>
        /// <returns>A Single value that contains the vector's squared length.</returns>
        public static float LengthSquared(Vector3 vector)
        {
            return vector.LengthSquared();
        }


        /// <summary>
        /// Returns the square of the length of a 3-D vector.
        /// </summary>
        /// <returns>A Single value that contains the vector's squared length.</returns>
        public float LengthSquared()
        {
            return x * x + y * y + z * z;
        }
        #endregion

        #region Maximize
        /// <summary>
        /// Returns a 3-D vector that is made up of the largest components of two 3-D vectors.
        /// </summary>
        /// <param name="vector1">Source Vector3f structure.</param>
        /// <param name="vector2">Source Vector3f structure.</param>
        /// <returns>A Vector3f structure that is made up of the largest components of the two vectors.</returns>
        public static Vector3 Maximize(Vector3 vector1, Vector3 vector2)
        {
            Vector3 result;
            result.x = System.Math.Max(vector1.x, vector2.x);
            result.y = System.Math.Max(vector1.y, vector2.y);
            result.z = System.Math.Max(vector1.z, vector2.z);
            return result;
        }

        /// <summary>
        /// Returns a 3-D vector that is made up of the largest components of two 3-D vectors.
        /// </summary>
        /// <param name="vector">Source Vector3f structure.</param>
        public void Maximize(Vector3 vector)
        {
            this.x = System.Math.Max(this.x, vector.x);
            this.y = System.Math.Max(this.y, vector.y);
            this.z = System.Math.Max(this.z, vector.z);
        }
        #endregion

        #region Minimize
        /// <summary>
        /// Returns a 3-D vector that is made up of the smallest components of two 3-D vectors.
        /// </summary>
        /// <param name="vector1">Source Vector3f structure.</param>
        /// <param name="vector2">Source Vector3f structure.</param>
        /// <returns>A Vector3f structure that is made up of the smallest components of the two vectors.</returns>
        public static Vector3 Minimize(Vector3 vector1, Vector3 vector2)
        {
            Vector3 result;
            result.x = System.Math.Min(vector1.x, vector2.x);
            result.y = System.Math.Min(vector1.y, vector2.y);
            result.z = System.Math.Min(vector1.z, vector2.z);
            return result;
        }

        /// <summary>
        /// Returns a 3-D vector that is made up of the smallest components of two 3-D vectors.
        /// </summary>
        /// <param name="vector">Source Vector3f structure.</param>
        public void Minimize(Vector3 vector)
        {
            this.x = System.Math.Min(this.x, vector.x);
            this.y = System.Math.Min(this.y, vector.y);
            this.z = System.Math.Min(this.z, vector.z);
        }
        #endregion

        #region Multiply
        /// <summary>
        /// Multiplies a 3-D vector by a Single value.
        /// </summary>
        /// <param name="vector">Source Vector3f structure.</param>
        /// <param name="factor">Source Single value used as a multiplier.</param>
        /// <returns>A Vector3f structure that is multiplied by the Single value.</returns>
        public static Vector3 Multiply(Vector3 vector, float factor)
        {
            Vector3 result;
            result.x = vector.x * factor;
            result.y = vector.y * factor;
            result.z = vector.z * factor;
            return result;
        }

        /// <summary>
        /// Multiplies a 3-D vector by a Single value.
        /// </summary>
        /// <param name="factor">Source Single value used as a multiplier.</param>
        public void Multiply(float factor)
        {
            this.x = this.x * factor;
            this.y = this.y * factor;
            this.z = this.z * factor;
        }
        #endregion

        #region Negation
        /// <summary>
        /// Returns a vector pointing in the opposite direction.
        /// </summary>
        /// <param name="vector">Source vector.</param>
        /// <returns>Vector pointing in the opposite direction.</returns>
        public static Vector3 Negate(Vector3 vector)
        {
            return -vector;
        }

        /// <summary>
        /// Returns a vector pointing in the opposite direction.
        /// </summary>
        /// <param name="vector">Source vector.</param>
        /// <returns>Vector pointing in the opposite direction.</returns>
        public static Vector3 operator -(Vector3 vector)
        {
            return new Vector3(-vector.x, -vector.y, -vector.z);
        }
        #endregion

        #region Normalize
        /// <summary>
        /// Returns the normalized version of a 3-D vector.
        /// </summary>
        public void Normalize()
        {
            float length = Length();
            x /= length;
            y /= length;
            z /= length;
        }

        /// <summary>
        /// Returns the normalized version of a 3-D vector.
        /// </summary>
        /// <param name="vector">Source Vector3f structure.</param>
        /// <returns>A Vector3f structure that is the normalized version of the specified vector.</returns>
        public static Vector3 Normalize(Vector3 vector)
        {
            float length = vector.Length();
            return new Vector3(vector.x / length, vector.y / length, vector.z / length);
        }
        #endregion

        #region Scale
        /// <summary>
        /// Scales a 3-D vector.
        /// </summary>
        /// <param name="vector">Source Vector3f structure.</param>
        /// <param name="factor">Scaling value.</param>
        /// <returns></returns>
        public static Vector3 Scale(Vector3 vector, float factor)
        {
            Vector3 result;
            result.x = vector.x * factor;
            result.y = vector.y * factor;
            result.z = vector.z * factor;
            return result;
        }

        /// <summary>
        /// Scales a 3-D vector.
        /// </summary>
        /// <param name="factor">Scaling value.</param>
        public void Scale(float factor)
        {
            this.x = this.x * factor;
            this.y = this.y * factor;
            this.z = this.z * factor;
        }
        #endregion

        #region Subtraction
        /// <summary>
        /// Subtracts two 3-D vectors.
        /// </summary>
        /// <param name="vector">Source Vector3f structure to subtract from the current instance.</param>
        public void Substract(Vector3 vector)
        {
            this.x = this.x - vector.x;
            this.y = this.y - vector.y;
            this.z = this.z - vector.z;
        }

        /// <summary>
        /// Subtracts two 3-D vectors.
        /// </summary>
        /// <param name="vector1">Source Vector3f structure to the left of the subtraction operator.</param>
        /// <param name="vector2">Source Vector3f structure to the right of the subtraction operator.</param>
        /// <returns>A Vector3f structure that is the result of the operation.</returns>
        public static Vector3 Subtract(Vector3 vector1, Vector3 vector2)
        {
            return new Vector3(vector1.x - vector2.x, vector1.y - vector2.y, vector1.z - vector2.z);
        }
        #endregion

        #region ToArray
        /// <summary>
        /// Returns a float array with three components: array[0] = x, array[1] = y, array[2] = z
        /// </summary>
        /// <returns></returns>
        public float[] toArray()
        {
            float[] array = new float[3];
            array[0] = x;
            array[1] = y;
            array[2] = z;
            return array;
        }
        #endregion

        #region ToString
        /// <summary>
        /// Obtains a string representation of the current instance.
        /// </summary>
        /// <returns>String that represents the object.</returns>
        public override string ToString()
        {
            return "\nx: " + x.ToString() + "\ny: " + y.ToString() + "\nz: " + z.ToString();
        }
        #endregion

        #region TransformCoordinate
        /// <summary>
        /// Transforms a 3-D vector or an array of 3-D vectors by a given matrix, projecting the result back into w = 1.
        /// </summary>
        /// <param name="matrix">Source Matrixf structure.</param>
        public void TransformCoordinate(Matrix matrix)
        {
            this = this * matrix;
        }

        /// <summary>
        /// Transforms a 3-D vector or an array of 3-D vectors by a given matrix, projecting the result back into w = 1.
        /// </summary>
        /// <param name="vector">Source Vector3 structure.</param>
        /// <param name="matrix">Source Matrix structure.</param>
        /// <returns>A Vector3f structure that represents the results of the method.</returns>
        public static Vector3 TransformCoordinate(Vector3 vector, Matrix matrix)
        {
            return vector * matrix;
        }

        /// <summary>
        /// Transforms a 3-D vector or an array of 3-D vectors by a given matrix, projecting the result back into w = 1.
        /// </summary>
        /// <param name="vectors">Array of source Vector3 structures.</param>
        /// <param name="matrix">Source Matrix structure.</param>
        /// <returns>Array of Vector3 structures that represent the results of the method.</returns>
        public static Vector3[] TransformCoordinate(Vector3[] vectors, Matrix matrix)
        {
            Vector3[] result = new Vector3[vectors.Length];
            for (int i = 0; i < vectors.Length; i++)
            {
                result[i] = vectors[i] * matrix;
            }
            return result;
        }
        #endregion

        // Operators

        #region op_Addition
        /// <summary>
        /// Adds two 3-D vectors.
        /// </summary>
        /// <param name="vector1">Source Vector3.</param>
        /// <param name="vector2">Source Vector3.</param>
        /// <returns>Sum of the two Vector3 structures.</returns>
        public static Vector3 operator +(Vector3 vector1, Vector3 vector2)
        {
            return new Vector3(vector1.x + vector2.x, vector1.y + vector2.y, vector1.z + vector2.z);
        }
        #endregion

        #region op_Equality
        /// <summary>
        /// Compares the current instance of a class to another instance to determine whether they are the same.
        /// </summary>
        /// <param name="vector1">The Vector3f structure to the left of the equality operator.</param>
        /// <param name="vector2">The Vector3f structure to the right of the equality operator.</param>
        /// <returns></returns>
        public static bool operator ==(Vector3 left, Vector3 right)
        {
            return (left.x == right.x && left.y == right.y && left.z == right.z);
        }
        #endregion

        #region op_Inequality
        /// <summary>
        /// Compares the current instance of a class to another instance to determine whether they are different.
        /// </summary>
        /// <param name="left">The Vector3f structure to the left of the inequality operator.</param>
        /// <param name="right">The Vector3f structure to the right of the inequality operator.</param>
        /// <returns></returns>
        public static bool operator !=(Vector3 left, Vector3 right)
        {
            return !(left.x == right.x && left.y == right.y && left.z == right.z);
        }
        #endregion

        #region op_Multiply
        /// <summary>
        /// Determines the product of a single value and a 3-D vector.
        /// </summary>
        /// <param name="vector">Source Vector3f structure.</param>
        /// <param name="factor">Source Single structure.</param>
        /// <returns>A Vector3f structure that is the product of the right and left parameters.</returns>
        public static Vector3 operator *(Vector3 vector, float factor)
        {
            return new Vector3(vector.x * factor, vector.y * factor, vector.z * factor);
        }

        /// <summary>
        /// Determines the product of a single value and a 3-D vector.
        /// </summary>
        /// <param name="factor">Source Single structure.</param>
        /// <param name="vector">Source Vector3f structure.</param>
        /// <returns>A Vector3f structure that is the product of the right and left parameters.</returns>
        public static Vector3 operator *(float factor, Vector3 vector)
        {
            return new Vector3(vector.x * factor, vector.y * factor, vector.z * factor);
        }
        #endregion

        #region op_Subtraction
        /// <summary>
        /// Subtracts two 3-D vectors.
        /// </summary>
        /// <param name="vector1">Source Vector3f structure to the left of the subtraction operator.</param>
        /// <param name="vector2">Source Vector3f structure to the right of the subtraction operator.</param>
        /// <returns>A Vector3f structure that is the result of the operation.</returns>
        public static Vector3 operator -(Vector3 vector1, Vector3 vector2)
        {
            return new Vector3(vector1.x - vector2.x, vector1.y - vector2.y, vector1.z - vector2.z);
        }
        #endregion

    }
}
